﻿/*
* jVal - dynamic jquery form field validation framework
*      version 0.1.5
* Author: Jim Palmer
* Released under MIT license.
*/
(function ($) {
    // core jVal function - returns TRUE if all jVal validation passes, false if not
    $.fn.jVal = function (options) {
        $.fn.jVal.clean(this);
        // clear jVal warnings
        if ((options || '').toString().toUpperCase() == 'CLEAN')
            return this; // chainable
        $.fn.jVal.defaults = $.extend($.fn.jVal.defaults, options);
        $(this).stop().find('.jfVal,.jValCover').stop().remove();
        var passVal = true, dm = $.fn.jVal.defaults.message, ds = $.fn.jVal.defaults.style;
        $(this).find('.jVal,[jVal]:not(:disabled):visible').each(
                        function () {
                            var cmd = $(this).data('jVal');
                            if (typeof cmd !== 'object') eval('cmd = ' + ($(this).data('jVal') || $(this).attr('jVal')) + ';');
                            //$.fn.jVal.clean(this);
                            if (cmd instanceof Object && cmd.valid instanceof RegExp && !cmd.valid.test($(this).val())) {
                                $.fn.jVal.showWarning(cmd.target || this, cmd.message || dm, cmd.autoHide || false, cmd.styleType || ds);
                                passVal = false;
                            } else if (cmd instanceof Object && cmd.valid instanceof Function) {
                                var testFRet = cmd.valid($(this).val(), this);
                                if (testFRet === false || testFRet.length > 0) {
                                    $.fn.jVal.showWarning(cmd.target || this, testFRet || cmd.message || dm, cmd.autoHide || false, cmd.styleType || ds);
                                    passVal = false;
                                }
                            } else if ((cmd instanceof RegExp && !cmd.test($(this).val())) || (cmd instanceof Function && !cmd($(this).val()))) {
                                $.fn.jVal.showWarning(cmd.target || this, dm, cmd.autoHide || false, cmd.styleType || ds);
                                passVal = false;
                            }
                        }
                );
        return passVal;
    };
    // showWarning utility function
    $.fn.jVal.showWarning = function (elements, message, autoHide, styleType) {
        var par = $(elements).eq(0).parent();
        clearTimeout($(par).data('autoHide')) && $(par).data('autoHide', null);
        $.fn.jVal.clean(par);
        $(elements).css({ marginTop: '', position: '', borderColor: 'red' });
        var dbw = $.fn.jVal.defaults.border,
                        dp = $.fn.jVal.defaults.padding,
                        fieldWidth = 0, fieldHeight = 0,
                        absoluteLeft = $(elements).eq(0).position().left,
                        absoluteTop = $(elements).eq(0).position().top;
        // normalize multi-element coordinates
        $(elements).each(function () {
            fieldWidth += $(this).outerWidth(true);
            fieldHeight = Math.max($(elements).outerHeight(true), fieldHeight);
            absoluteLeft = Math.min($(this).position().left, absoluteLeft);
            absoluteTop = Math.min($(this).position().top, absoluteTop);
        });
        var fwidth = 0, tPos = absoluteTop - dp - dbw, lPos = absoluteLeft + dp, rPos = absoluteLeft + fieldWidth + dp,
                        ph = (fieldHeight + (dp * 2)), fhp = fieldHeight + dp + dbw, fwp = fieldWidth + dp,
                        clips = [
                                'rect(0px, 5000px, ' + (dp + dbw) + 'px, 0px)', // top
                                'rect(' + fhp + 'px, 5000px, 5000px, 0px)', // bottom
                                'rect(' + dp + 'px, ' + dp + 'px, 5000px, 0px)', // left
                                'rect(' + dp + 'px, 5000px, 5000px, ' + fwp + 'px)']; // right
        // fixed alerts, no wrapping
        if (!$.fn.jVal.defaults.wrap) {
            fieldWidth = ph = fph = 12;
            rPos = lPos;
        }
        $(elements).eq(0).before(
                        '<div class="jValSpacer jValSpacer' + styleType + '" style="line-height:' + ph + 'px; height:' + ph + 'px; clip:' + clips[0] + '; ' +
                                'left:' + (absoluteLeft - dp) + 'px; top:' + tPos + 'px; ' +
                                'width:' + (fieldWidth + (dp * 2)) + 'px;" />' +
                        '<div class="jfVal' + (styleType ? ' jfVal' + styleType : '') + '" style="left:' + lPos + 'px; ' +
                                'top:' + tPos + 'px;">' +
                                '<div class="icon' + (styleType ? ' icon' + styleType : '') + '" style="height:' + ph + 'px;"><div class="iconbg" /></div>' +
                                '<div class="content' + (styleType ? ' content' + styleType : '') + '" style="height:' + ph + 'px; line-height:' + ph + 'px;">' +
                                        '<span class="message' + styleType + '">' + message + '</span>' +
                                '</div>' +
                        '</div>')
                        .parent().find('.jfVal>*').each(function () { fwidth += $(this).outerWidth() }).end()
                        .find('.jfVal').width(fwidth + 20);
        for (var si = 1; si < 4; si++)
            $(par).find('.jValSpacer:first').before(
                                $(par).find('.jValSpacer:first').clone().css({ clip: clips[si] }));
        // autoHide = set spacer width + add autohide function to fx queue
        if (autoHide)
            $(par).data('autoHide', setTimeout(function () { $(par).find('.jfVal').animate({ left: lPos, opacity: 0 }, 200, function () { $.fn.jVal.clean(par); }); }, 2000))
                                .find('.jfVal').css({ 'left': rPos });
        else
            $(par).find('.jfVal').css({ opacity: 0 }).animate({ left: rPos, opacity: 1 }, 200);
    };
    // validate key stroke
    $.fn.jVal.valKey = function (keyRE, e, cF, cA) {
        var ek = (typeof (e.keyCode) != 'undefined'), ec = (typeof (e.charCode) != 'undefined'),
                        k = e.keyCode, c = e.charCode, ks = k.toString();
        // return if invalid regex
        if (!(keyRE instanceof RegExp))
            return false;
        // test for ENTER key and cF being valid function otherwise return true
        if (/^13$/.test(String(k || c))) {
            try { (this[cF]) ? this[cF](cA) : eval(cF); } catch (e) { return true; }
            return -1;
        }
        // otherwise test for valid keys supported by the regex allowing meta keys by default
        if (e.ctrlKey || e.shiftKey || e.metaKey || (
                                ek && k > 0 && keyRE.test(String.fromCharCode(k))) ||
                                (ec && c > 0 && String.fromCharCode(c).search(keyRE) != (-1)) ||
                                (ec && c != k && ek && ks.search(/^(8|9|45|46|35|36|37|39)$/) != (-1)) ||
                                (ec && c == k && ek && ks.search(/^(8|9)$/) != (-1) ||
                                (!ec && ek && ks.search(/^(8|9|45|46|35|36|37|39)$/) != (-1)))
                        ) {
            return 1;
        } else {
            return 0;
        }
    };
    // clear all displayed jVal warnings within scope
    $.fn.jVal.clean = function (target) {
        $(target)
                        .find('.jfVal,.jValSpacer').stop().remove().end()
                        .find('.jVal,[jVal]').css({ position: '', borderColor: '', left: '0px', top: '0px' }).parent().find('.jValRelWrap').remove();
    };
    $.fn.jVal.init = function (options) {
        $.fn.jVal.defaults = $.extend($.fn.jVal.defaults, options);
        $('.jVal,[jVal]').filter(':not(:disabled)').unbind("blur").bind("blur", function (e) {
            if ($.fn.jVal.defaults.blurCheck || $(this).hasClass('jValBlur'))
                $(this).parent().jVal();
        });
        var keyFunc = function (e) {
            eval('var cmd = ' + ($(this).data('jValKey') || $(this).attr('jValKey') || $(this).data('jValKeyUp') || $(this).attr('jValKeyUp')) + ';');
            var keyTest, ds = $.fn.jVal.defaults.style, dkm = $.fn.jVal.defaults.keyMessage,
                                autoHide = typeof (cmd.autoHide) != 'undefined' ? cmd.autoHide : true;
            if (cmd instanceof Object && cmd.valid instanceof Function) {
                keyTest = cmd.valid(e, this);
                if (keyTest === false || keyTest.length > 0) {
                    $.fn.jVal.clean(cmd.target || this);
                    // only show warning and allow keypress event to return true
                    $.fn.jVal.showWarning(cmd.target || this, (keyTest || (cmd.message || dkm).replace('%c', String.fromCharCode(e.keyCode || e.charCode))), autoHide, cmd.styleType || ds);
                } else
                    $.fn.jVal.clean($(cmd.target || this).parent());
            } else {
                keyTest = $.fn.jVal.valKey(((cmd instanceof Object) ? cmd.valid : cmd), e, (cmd instanceof Object) ? cmd.cFunc : null, (cmd instanceof Object) ? cmd.cArgs : null);
                if (keyTest == 0) {
                    $.fn.jVal.clean(cmd.target || this);
                    $.fn.jVal.showWarning(cmd.target || this, ((cmd instanceof Object && cmd.message) || dkm).replace('%c', String.fromCharCode(e.keyCode || e.charCode)), autoHide, cmd.styleType || ds);
                    return false;
                } else if (keyTest == -1)
                    return false;
                else
                    $.fn.jVal.clean($(cmd.target || this).parent());
            }
            return true;
        };
        $('.jValKey,[jValKey]').filter(':not(:disabled)').unbind("keypress").bind("keypress", keyFunc);
        $('.jValKeyUp,[jValKeyUp]').filter(':not(:disabled)').unbind("keyup").bind("keyup", keyFunc);
        return this; // chainable
    };
    // jVal defaults
    $.fn.jVal.defaults = {
        blurCheck: false,
        message: 'Invalid entry',
        style: 'pod',
        keyMessage: '"%c" Invalid character',
        padding: 3,
        border: 1,
        wrap: true
    };
    // automatically init on dom load
    $($.fn.jVal.init);
})(jQuery);